# -*- coding: utf-8 -*-
"""
@Time ： 2020-11-19 9:30
@Auth ： lixin
@Description：

"""
from sklearn import linear_model
import math
from sklearn import metrics
from sklearn.ensemble import RandomForestClassifier
from sklearn import svm
import api as runs
import numpy as np
import os
import lightgbm as lgb
from sklearn import neural_network
from xgboost.sklearn import XGBClassifier
from sklearn.ensemble import GradientBoostingClassifier
import json
import pickle
import fasttext
from algo.DigitalTypeClassifier import DigitalTypeClassifier
from algo.DigitalTypeRegressor import DigitalTypeRegressor
from algo.AssociationRuleAnalysis import AssociationRuleAnalysis
from algo.TimeSeriesAnalysis import TimeSeriesAnalysis
from algo.DigitalTypeCluster import DigitalTypeCluster
from algo.ImageTypeClassifier import ImageTypeClassifier
from algo.TextClassifier import TextClassifier
from algo.AudioClassifier import AudioClassifier
# from algo.FaceDetection import FaceDetection
from algo.ObjectDetection import ObjectDetection
from detectutils import detectutils
from imageai.Detection.Custom import CustomObjectDetection

class ModelTrainMange():
    def __init__(self,model_type, model_name, model_params):
        self.model_type = model_type # 分类/回归等等
        self.model_name = model_name # 逻辑分类/svc等等
        self.model_params = model_params
        self.model=None
        self.choose_model()
        # return self.model
    #逻辑回归、支持向量机、随机森林、GBDT、xgboost等;线性回归、支持向量回归、神经网络等
    # '1001':分类；'1002':回归；'1003':聚类；'1004':关联规则分析；'1005':时间序列分析；'1006':文本分类；'1007':图像分类；
    # '1008':语音分类；'1010':物体检测；
    def choose_model(self):
        if self.model_type == '1001':
            self.model=DigitalTypeClassifier(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1002':
            self.model=DigitalTypeRegressor(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1003':
            self.model=DigitalTypeCluster(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1004':
            self.model = AssociationRuleAnalysis(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1005':
            self.model = TimeSeriesAnalysis(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1006':
            self.model = TextClassifier(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1007':
            self.model = ImageTypeClassifier(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1008':
            self.model = AudioClassifier(self.model_type, self.model_name, self.model_params)
        # elif self.model_type == '1009':
        #     self.model = FaceDetection(self.model_type, self.model_name, self.model_params)
        elif self.model_type == '1010':
            self.model = ObjectDetection(self.model_type, self.model_name, self.model_params)

    # 评估
    def map_format(self, map, keep):  # keep表示保留几位有效数字
        for key in map.keys():
            try:
                map[key] = round(map[key], keep)
            except:
                map[key] = map[key]
        return map

    #平均准确率
    def average_acc(self, y_t, y_p):
        def average_acc_single(y_t_s, y_p_s):
            rate = np.abs(np.min([y_t_s, y_p_s])/np.max([y_t_s, y_p_s]))
            return rate
        vfunc_average_acc = np.vectorize(average_acc_single)
        rate_list = vfunc_average_acc(y_t, y_p)
        result = np.round(np.mean(rate_list), 2)
        return result

    def evaluate(self,y_true, y_pred):
        metrics_dict = {}
        if self.model_type == '1001' or self.model_type == '1006' \
                or self.model_type == '1007' or self.model_type == '1008' :
            #分类指标: 准确率,精确率，召回率，f1,roc曲线，auc曲线
            metrics_dict['accuracy'] = metrics.accuracy_score(y_true,y_pred)
            metrics_dict['fl_score'] = metrics.f1_score(y_true,y_pred, average='macro')
            metrics_dict['precision'] = metrics.precision_score(y_true, y_pred, average='macro')
            metrics_dict['recall'] = metrics.recall_score(y_true, y_pred, average='macro')
        elif self.model_type == '1002' or self.model_type == '1005':# 绝对平均误差率，#拟合优度
            #metrics_dict['average_accuracy'] = self.average_acc(y_true, y_pred)
            metrics_dict['rmse'] = np.sqrt(metrics.mean_squared_error(y_true, y_pred))
            metrics_dict['r2_score'] = metrics.r2_score(y_true, y_pred)
            if 0 in list(y_true): #如果有0值，该公式不可用,设该值为-1; mape值>100%表示劣质模型
                metrics_dict['mape'] = -1
            else: 
                metrics_dict['mape'] = np.mean(abs((y_pred - y_true) / y_true))
            metrics_dict['mae'] = metrics.mean_absolute_error(y_true, y_pred)

        elif  self.model_type in ['1009','1010']:
            miss_detection, false_detection, true_detection = detectutils.detect_evaluate(y_true)
            if true_detection == 0 and false_detection == 0:
                metrics_dict['accuracy'] = 0
            else:
                metrics_dict['accuracy'] = true_detection / (true_detection + false_detection)
            metrics_dict['precision'] = -1
            metrics_dict['recall'] = true_detection / (true_detection + miss_detection)
            if metrics_dict['accuracy'] != 0 and metrics_dict['recall'] != 0:
                metrics_dict['fl_score'] = 2 / ((1 / metrics_dict['recall']) + (1 / metrics_dict['accuracy']))
            else:
                metrics_dict['fl_score'] = 0

        for key, val in metrics_dict.items():
            if np.isnan(val):
                metrics_dict[key] = 0
        #格式化字典, 保留两位有效数字
        metrics_dict = self.map_format(metrics_dict, 2)
        return metrics_dict

    def set_model_name(self, model_name):
        self.model_name = model_name
        return 1
    # 新增读取模型方法，读取先需要先定义类，对于1006采用fasttext读取，其余采用pickle，仅输入模型文件路径
    def load_model(self, model_path):
        if self.model_type == '1006':
            self.model.model = fasttext.load_model(model_path)
        elif self.model_type == '1010':
            f = open("../data/test.txt", "r", encoding="utf-8")
            data = json.load(f)
            self.model.model['model'] = CustomObjectDetection()
            self.model['model'].setModelTypeAsYOLOv3()
            self.model['model'].setJsonPath(data['json'])
            self.model['model'].setModelPath(data['model_path'])
            self.model['model'].loadModel()
        else:
            with open(model_path, 'rb') as f:
                self.model.model = pickle.load(f)

    def get_model_name(self):
        return self.model_name

    def set_model_type(self, model_type):
        self.model_type = model_type
        return 1

    def get_model_type(self):
        return self.model_type

    def set_model(self, model):
        self.model = model
        return 1

    def get_model(self):
        return self.model

    def train(self,data):

        result=self.model.train(data)
        return result

    def predict(self,data):
        y_pred=self.model.predict(data)
        return y_pred

    def logistic_formula(self, y_train, map_result):
        clf=self.model.get_model()
        feature_list = map_result['feature_name']
        label_map = map_result['label']
        label_list = np.unique(y_train)
        # 如果label做了映射，那么需要还原
        if len(label_map) != 0:
            for index in range(len(label_list)):
                label_list[index] = label_map[str(label_list[index])]
        formula_list = {}
        if self.model_name == '线性回归' or (self.model_name == '逻辑回归' and len(label_list)==2):
            formula = ''
            if self.model_name == '线性回归':
                w_list = clf.coef_
                b = clf.intercept_
            else:
                w_list = clf.coef_[0]
                b = clf.intercept_[0]
            for j in range(len(feature_list)):
                w = round(w_list[j], 3)
                if j == 0:
                    formula = str(w) + '*' + feature_list[j]
                if j != 0:
                    if w >= 0:
                        formula = formula + '+' + str(w) + '*' + feature_list[j]
                    else:
                        formula = formula + str(w) + '*' + feature_list[j]
            if b >= 0:
                formula = formula + '+' + str(round(b, 3))
            else:
                formula = formula + str(round(b, 3))
            formula_list[map_result['label_name']] = formula

        elif self.model_name == '逻辑回归' and len(label_list)>2:

            for i in range(len(clf.coef_)):
                formula = ''
                for j in range(len(feature_list)):
                    w = round(clf.coef_[i][j], 3)
                    if j == 0:
                        formula = str(w) + '*' + feature_list[j]
                    if j != 0:
                        if w >= 0:
                            formula = formula + '+' + str(w) + '*' + feature_list[j]
                        else:
                            formula = formula + str(w) + '*' + feature_list[j]
                b = clf.intercept_[i]
                if b >= 0:
                    formula = formula + '+' + str(round(b, 3))
                else:
                    formula = formula + str(round(b, 3))
                formula_list[label_list[i]] = formula
        else:
            return ''


        formula_list = json.dumps(formula_list, ensure_ascii=False)
        if len(formula_list) > 1000:
            return json.dumps({'error':'公式太长无法展示'}, ensure_ascii=False)
        return formula_list

